Code Structure
Overview
The project adopts the MVVM (Model-View-ViewModel) architectural pattern to facilitate a clear segregation between business logic and the user interface. This pattern encompasses three key components: Model, View, and ViewModel. The Model holds pure data without any business logic, while the View serves as the user interface for interacting with the application. The ViewModel acts as an intermediary between the Model and the View, encapsulating business logic, modifying data from the Model, and preparing it for presentation in the View. By leveraging the MVVM pattern, the project enhances maintainability, testability, and scalability by promoting modular design principles and isolating concerns effectively.
PROJECT STRUCTURE
The project structure primarily comprises the 'Application' and 'Plugins' folders. Within the 'Application' folder, the entry point of the app and configurations for different environments are housed. The 'Plugins' folder contains all supported plugins, each designed to be standalone and independent. This folder is subdivided into 'Resources' and 'Sources'. 'Resources' hold assets specific to each plugin, while each 'Sources' folder includes 'Base', 'Navigation', 'Screens', 'APIServices', 'Utils' and 'Analytics'.
Base
In this section, a class conforming to the OCPlugin
protocol is included, serving as the entry point for a specific plugin. The 'Base' class initiates the 'Router' to facilitate navigation across various screens within the plugin.
Navigation
This section features a router that conforms to the OCSceneRouter
protocol, along with a subfolder housing scene factory structs that conform to SwiftUISceneFactory
for all of the screens. The 'router' facilitates navigation to various screens within the plugin and other plugins. For SwiftUI navigation, UIKit is utilized to embed a View
within a UINavigationController
.
Example
class PluginScreen1ViewModel {
private var sceneCoordinator: OCSceneCoordinator?
init(sceneCoordinator: OCSceneCoordinator?) {
self.sceneCoordinator = sceneCoordinator
}
@MainActor
func navigateToScreen2() {
guard let sceneCoordinator else { return }
let router = PluginSceneRouter(sceneCoordinator: sceneCoordinator)
router.navigate(to: PluginScene.screen2())
}
}
Screens
This section encompasses all screens within a specific plugin. Each folder represents a single screen, structured with Model, View, and ViewModel subfolders in alignment with the MVVM architecture.
APIServices
This section presents vital endpoints essential for the plugin, accompanied by configurations for each endpoint and a PluginServiceManager
. Endpoints are organized within an enum that conforms to the OCRequestable
protocol, allowing thorough configuration for API calls, including path, method, body/query parameters, and custom headers. Invocation of an endpoint from a ViewModel utilizes the PluginServiceManager
, which executes the configured request through the OCNetworkManager
.
enum PluginEndPoint {
case service1
case service2
}
extension PluginEndPoint: OCRequestable {
var path: String {
switch self {
case .service1:
return "api/service1"
case .service2:
return "api/service2"
}
}
var method: String {
switch self {
case .service1:
return .post
case .service2:
return .get
}
}
var parameters: String {
switch self {
case .service1:
return [PluginConstants.API.ParameterKey.Key1: Value1]
case .service2:
return nil
}
}
var queryParameters: String {
switch self {
case .service1:
return nil
case .service2:
return [PluginConstants.API.ParameterKey.Key2: Value2]
}
}
}
Utils
This section contains all the supporting and reusable code necessary for building the screens. It encompasses extensions of various types, constants specific to the plugin, and other helpers.
Analytics
This is an enum that conforms to the OCAnalyticsEventProtocol
and serves solely to facilitate analytics events originating from the plugin. It encompasses various events derived from all screens within the plugin.
Tree diagram of Plugin Structure
PluginName
├── Sources
│ ├── Base
│ │ └── Plugin
│ ├── Navigation
│ │ ├── PluginSceneRouter
│ │ └── SceneFactory
│ │ ├── Screen1SceneFactory
│ │ └── Screen2SceneFactory
│ ├── Screens
│ │ ├── Screen1
│ │ │ ├── Model
│ │ │ ├── View
│ │ │ └── ViewModel
│ │ └── Screen2
│ │ ├── Model
│ │ ├── View
│ │ └── ViewModel
│ ├── APIServices
│ │ ├── PluginEndPoint
│ │ └── PluginServiceManager
│ ├── Utils
│ │ ├── PluginConstants
│ │ ├── String+PluginExtension
│ │ └── SomeHelper
│ └── Analytics
│ └── PluginAnalyticsEvent
└── Resources
└── Assets
Example usage of APIServices in a ViewModel
class PluginScreen1ViewModel {
private var sceneCoordinator: OCSceneCoordinator?
private let serviceManager: PluginServiceManager
init(sceneCoordinator: OCSceneCoordinator?,
serviceManager: ChallengeServiceType) {
self.sceneCoordinator = sceneCoordinator
self.serviceManager = serviceManager
}
func fetchService1() {
Task {
// Show activity indicator
updateLoading(with: true)
do {
let dataModel: PluginScreen1APIDataModel = try await serviceManager.fetchService1(PluginEndPoint.service1)
// Update UI with response here
} catch {
// Error handling
}
// Hide activity indicator
updateLoading(with: false)
}
}
}